package com.xxx.springboot.distributedsession;


import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Enumeration;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;

import lombok.Data;
import lombok.extern.log4j.Log4j2;

/**
 * 自定义分布式session
 */
@Data
@Log4j2
@SuppressWarnings("deprecation")
public class DistributedHttpSession implements HttpSession {

    private static final SessionCacheEngine SESSION_CACHE_ENGINE = new RedisSessionCacheEngine();

    private static final String SESSION_CACHE_KEY = "session:";
    private final String sessionId;
    private final String sessionCacheKeyHeader;
    private final String sessionCacheKeyAttribute;
    private final ServletContext context;
    private DistributedSessionHeader sessionHeader;
    private DistributedSessionAttribute sessionAttribute;
    /**
     * 会话最大活动时间，单位秒
     */
    private int maxInactiveInterval = -1;
    /**
     * 是否需要更新缓存
     */
    private boolean update = false;
    /**
     * 会话有效状态
     */
    private boolean invalid = false;

    public DistributedHttpSession(ServletContext servletContext, String sessionId) {
        this.context = servletContext;
        this.sessionId = sessionId;
        sessionCacheKeyHeader = SESSION_CACHE_KEY + this.sessionId + ":header";
        sessionCacheKeyAttribute = SESSION_CACHE_KEY + this.sessionId + ":attribute";
    }

    @Override
    public long getCreationTime() {
        return sessionHeader.getCreateTime();
    }

    @Override
    public String getId() {
        return sessionId;
    }

    @Override
    public long getLastAccessedTime() {
        return sessionHeader.getLastAccessTime();
    }

    @Override
    public ServletContext getServletContext() {
        return context;
    }

    @Override
    public int getMaxInactiveInterval() {
        return maxInactiveInterval;
    }

    @Override
    public void setMaxInactiveInterval(int interval) {
        this.maxInactiveInterval = interval;
    }

    @Override
    public HttpSessionContext getSessionContext() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Object getAttribute(String name) {
        checkSessionInvalid();
        return findDistributedSessionAttribute().getAttribute(name);
    }

    @Override
    public Object getValue(String name) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        checkSessionInvalid();
        return new AttributeNamesEnumeration(findDistributedSessionAttribute().getAttributeNames());
    }

    @Override
    public String[] getValueNames() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setAttribute(String name, Object value) {
        checkSessionInvalid();
        try {
            checkSerializable(value);
        } catch (NotSerializableException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
        findDistributedSessionAttribute().putAttribute(name, (Serializable) value);
        update = true;
    }

    @Override
    public void putValue(String name, Object value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void removeAttribute(String name) {
        checkSessionInvalid();
        findDistributedSessionAttribute().removeAttribute(name);
        update = true;
    }

    @Override
    public void removeValue(String name) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void invalidate() {
        invalid = true;
    }

    @Override
    public boolean isNew() {
        checkSessionInvalid();
        return sessionHeader.isNewBuild();
    }

    /**
     * 初始化方法
     */
    public void init() {
        //缓存中是否已经存在对应的数据
        if (SESSION_CACHE_ENGINE.containsKey(sessionCacheKeyHeader)) {
            findDistributedSessionHeader();
            //设置新建标志为false
            sessionHeader.setNewBuild(false);
            update = false;
        } else {
            //cache中不存在对应的session，初始化一个
            initDistributedSessionHeader(true);
            update = true;
        }
    }

    /**
     * 初始化session header 和 attribute
     *
     * @param newBuild
     */
    private void initDistributedSessionHeader(boolean newBuild) {
        Calendar now = Calendar.getInstance();
        sessionHeader = new DistributedSessionHeader(now.getTimeInMillis());
        sessionHeader.setLastAccessTime(now.getTimeInMillis());
        sessionHeader.setNewBuild(newBuild);

        sessionAttribute = new DistributedSessionAttribute();
    }

    /**
     * 获取分布式存储中的session header
     *
     * @return
     */
    private void findDistributedSessionHeader() {
        Object value = SESSION_CACHE_ENGINE.get(sessionCacheKeyHeader);
        if (null == value) {
            initDistributedSessionHeader(false);
        } else {
            sessionHeader = (DistributedSessionHeader) value;
        }
    }

    /**
     * 从CACHE中获取SESSION ATTRIBUTE ，如果没有就创建一个新的
     *
     * @return
     */
    private DistributedSessionAttribute findDistributedSessionAttribute() {
        if (null == sessionAttribute) {
            DistributedSessionAttribute attribute = (DistributedSessionAttribute) SESSION_CACHE_ENGINE.get(
                    sessionCacheKeyAttribute);
            //如果返回null , 则新建一个CacheSessionAttribute
            if (attribute == null) {
                sessionAttribute = new DistributedSessionAttribute();
            } else {
                sessionAttribute = attribute;
            }
        }
        return sessionAttribute;
    }

    /**
     * 判断是否已经超过了最大活动时间。
     *
     * @return true超过，false没有超过。
     */
    public boolean isInvalid() {
        if (getMaxInactiveInterval() <= 0) {
            invalid = false;
        } else {
            long invalidMillis = getMaxInactiveInterval() * 1000;
            long lastAccessTime = getLastAccessedTime();
            long now = Calendar.getInstance().getTimeInMillis();
            invalid = (now - lastAccessTime) > invalidMillis;
        }
        return invalid;
    }

    /**
     * 检测session 有效性
     *
     * @throws IllegalStateException
     */
    private void checkSessionInvalid() throws IllegalStateException {
        if (invalid) {
            throw new IllegalStateException("Session is invalid.");
        }
    }

    /**
     * 设置session最后一次访问时间
     */
    public void access() {
        log.debug("sessionAccess|{}", this.getSessionId());
        sessionHeader.setLastAccessTime(Calendar.getInstance().getTimeInMillis());
    }

    private void checkSerializable(Object value) throws NotSerializableException {
        if (value == null) {
            return;
        }
        if (!(value instanceof Serializable)) {
            throw new NotSerializableException(value.getClass().getName());
        }
    }

    /**
     * 同步session cache数据
     *
     * @return true session还有效/false session已无效，可以删除cookie
     */
    public boolean synchronizationCache() {
        if (invalid) {
            removeRemoteSessionForCache();
            return false;
        } else {
            updateCacheSessionHeader(sessionHeader);
            if (update) {
                updateCacheSessionAttribute(sessionAttribute);
            } else {
                //如果session属性不需要更新的话，需要重置过期时间
                resetCacheSeesionAttributeExpire();
            }
            //更新完成,update标志设置为false
            update = false;
            return true;
        }
    }

    private void updateCacheSessionHeader(DistributedSessionHeader header) {
        SESSION_CACHE_ENGINE.put(sessionCacheKeyHeader, header, getMaxInactiveInterval());
    }

    private void updateCacheSessionAttribute(DistributedSessionAttribute attribute) {
        SESSION_CACHE_ENGINE.put(sessionCacheKeyAttribute, attribute, getMaxInactiveInterval());
    }

    private void resetCacheSeesionAttributeExpire() {
        SESSION_CACHE_ENGINE.resetExpire(sessionCacheKeyAttribute, getMaxInactiveInterval());
    }

    /**
     * 删除分布式存储中的session数据
     */
    private void removeRemoteSessionForCache() {
        SESSION_CACHE_ENGINE.remove(sessionCacheKeyHeader);
        SESSION_CACHE_ENGINE.remove(sessionCacheKeyAttribute);
    }
}
